package org.swxjava.util;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class BeanUtils {

	/** Logger */
	private static final Log LOG = LogFactory.getLog(BeanUtils.class);

	public static Object newInstance(String name) {
		try {
			Class c = getClassForName(name);
			return c.newInstance();
		} catch (Exception e) {
			LOG.error("Could not instanciate a class [" + name + "]", e);
			return null;
		}
	}
	
	public static Class getClassForName(String name) throws IllegalArgumentException {
		try {
			return Class.forName(name);
		} catch (Exception e) {
			throw new IllegalArgumentException("ServiceClass[" + name + "] could not found.", e);
		}
	}
	
	public static void setProperty(Object target, String name, Object value) {
		if(target == null) {
			return;
		}
		Class clazz = target.getClass();
		java.beans.Introspector.decapitalize(name);
		String setterMethodName = "set" + capitalize(name);
		Class argClass = (value == null) ? null : value.getClass();
		try {
			Method setter = target.getClass().getMethod(setterMethodName, argClass);
			setter.invoke(target, value);
		} catch(NoSuchMethodException e) {
			throw new IllegalArgumentException("Could not find the property named [" + name + "] in " + target.getClass().getSimpleName());
		} catch(InvocationTargetException e) {
			throw new IllegalArgumentException("Could not invoke the setter method named [" + setterMethodName + "] in " + target.getClass());
		} catch(IllegalAccessException e) {
			throw new IllegalArgumentException("Could not access the setter method named [" + setterMethodName + "] in " + target.getClass());
		}
	}
	
	public static String capitalize(String name) {
		if(name == null || name.length() == 0) {
			throw new IllegalArgumentException("name coould not be blank.");
		}
		char[] chars = name.toCharArray();
		chars[0] = Character.toUpperCase(chars[0]);
		return new String(chars);
	}
	
	public static final Object convert(Class targetClass, Object source) throws Exception {
		if(source == null) {
			return null;
		}
		Object result = null;
		
		if( Map.class.equals(targetClass) || isImplements(targetClass, Map.class) ) {
			// Map
			LOG.debug(" Map : " + targetClass.getSimpleName() + " from [" + source.getClass().getSimpleName() + ":" + source + "]");
			Map map = new HashMap();
			if(!(source instanceof Map)) {
				throw new IllegalArgumentException("Expected type is Map, but parameter type was " + source.getClass().getSimpleName());
			}
			map.putAll((Map)source);
			result = map;
			
		} else if( Set.class.equals(targetClass) || isImplements(targetClass, Set.class) ) {
			// Set
			LOG.debug(" Set : " + targetClass.getSimpleName() + " from [" + source.getClass().getSimpleName() + ":" + source + "]");
			Set set = new HashSet();
			set.addAll(getCollection(source));
			result = set;
			
		} else if( List.class.equals(targetClass) || isImplements(targetClass, List.class) ) {
			// List
			LOG.debug(" List : " + targetClass.getSimpleName() + " from [" + source.getClass().getSimpleName() + ":" + source + "]");
			List list = new ArrayList();
			Collection sourceCollection = getCollection(source);
			for(Iterator it = sourceCollection.iterator(); it.hasNext(); ) {
				Object listValue = it.next();
				if(listValue != null) {
					LOG.debug(" Object : " + listValue.getClass().getSimpleName() + " from [" + listValue.getClass().getSimpleName() + ":" + listValue + "]");
					listValue = convert(listValue.getClass(), listValue);
				}
				list.add(listValue);
			}
			result = list;
			
		} else if( targetClass.equals(String.class) ) {
			// String
			LOG.debug(" String : " + source);
			result = String.valueOf(source);
			
		} else if( isPrimitive(targetClass) ) {
			// Primitive
			LOG.debug(" Primitive : " + targetClass.getSimpleName() + " from [" + source.getClass().getSimpleName() + ":" + source + "]");
			result = convertPrimitive(targetClass, source);
			
		} else if( targetClass.equals(Date.class) ) {
			// Date
			LOG.debug(" Date : " + targetClass.getSimpleName() + " from [" + source.getClass().getSimpleName() + ":" + source + "]");
			result = new Date((Long) convertPrimitive(long.class, source));
			
		} else if( targetClass.equals(Object.class) ) {
			// Object
			LOG.debug(" Object : " + targetClass.getSimpleName() + " from [" + source.getClass().getSimpleName() + ":" + source + "]");
			result = convert(source.getClass(), source);
			
		} else {
			// Bean
			result = targetClass.newInstance();
			Map map = (Map) source;
			BeanInfo beanInfo = Introspector.getBeanInfo(targetClass);
			for(PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
				if(pd != null && pd.getWriteMethod() != null) {
					LOG.debug("Converting ... " + targetClass.getSimpleName() + "." + pd.getName() + "(" + pd.getPropertyType() + ")");
					Object args = convert(pd.getPropertyType(), map.get(pd.getName()));
					if(isPrimitive(pd.getPropertyType()) && args == null) {
						
					} else {
						pd.getWriteMethod().invoke(result, args);
					}
				}
			}
		}
		return result;
	}
	
	public static final boolean isPrimitive(Class type) {
		return (type.isPrimitive()
				|| type.equals(Byte.class)
				|| type.equals(Integer.class)
				|| type.equals(Short.class)
				|| type.equals(Long.class)
				|| type.equals(Double.class)
				|| type.equals(Float.class)
				|| type.equals(Boolean.class)
				|| type.equals(Character.class));
	}
	
	public static final boolean isExtends(Class target, Class ext) {
		Class superClass = target.getSuperclass();
		if(ext.equals(superClass)) {
			return true;
		} else if(superClass != null) {
			return isExtends(superClass, ext);
		} else {
			return false;
		}
	}
	
	public static final boolean isImplements(Class target, Class impl) {
		if(target.getInterfaces() == null) {
			return false;
		}
		for(Class targetInterface : target.getInterfaces()) {
			if(targetInterface == null) continue;
			if(impl.equals(targetInterface)) {
				return true;
			}
		}
		if(target.getSuperclass() != null) {
			return isImplements(target.getSuperclass(), impl);
		} else {
			return false;
		}
	}

	public static final Object convertPrimitive(Class type, Object value) {
		if(type.equals(byte.class) || type.equals(Byte.class)) {
			return ((Number) value).byteValue();
		} else if(type.equals(int.class) || type.equals(Integer.class)) {
			return ((Number) value).intValue();
		} else if(type.equals(short.class) || type.equals(Short.class)) {
			return ((Number) value).shortValue();
		} else if(type.equals(long.class) || type.equals(Long.class)) {
			return ((Number) value).longValue();
		} else if(type.equals(double.class) || type.equals(Double.class)) {
			return ((Number) value).doubleValue();
		} else if(type.equals(float.class) || type.equals(Float.class)) {
			return ((Number) value).floatValue();
		} else if(type.equals(boolean.class) || type.equals(Boolean.class)) {
			return (Boolean)value;
		} else if(type.equals(char.class) || type.equals(Character.class)) {
			return (Character)value;
		}  else {
			throw new IllegalArgumentException("Illegal primitive type : " + value);
		}
	}
	
	private static final Collection getCollection(Object obj) {
		if(obj.getClass().isArray()) {
			return Arrays.asList(obj);
		} else if(obj instanceof Collection) {
			return (Collection) obj;
		} else {
			throw new IllegalArgumentException("Expected type is Collection, but parameter type was " + obj.getClass().getSimpleName());
		}
	}
	
	public static final boolean isArray(Object obj) {
		if(obj == null) {
			return false;
		}
		return obj.getClass().isArray();
	}
}
